/*=====================================================================

  File:      Wireframe.cs
  Summary:   The application representation of a "wireframe"
  Date:	     May 27, 2006

---------------------------------------------------------------------

  This file is part of the Microsoft SQL Server Code Samples.
  Copyright (C) Microsoft Corporation.  All rights reserved.

This source code is intended only as a supplement to Microsoft
Development Tools and/or on-line documentation.  See these other
materials for detailed information regarding Microsoft code samples.

THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.

======================================================= */

using System;
using System.Drawing;
using System.Diagnostics.CodeAnalysis;
using System.Collections.Generic;

namespace Microsoft.Samples.SqlServer.DB3D
{
	// callers that want to be called-back to see progress as
	// the wireframe is transferred can provide a delegate with this
	// signature
	public delegate void WireframeProgress(int triangles);
	
	// A wireframe represents a whole drawing, composed of a list of triangles
	// and other state information. It can render itself and apply transformations 
    // such as rotation and scale.
	public class Wireframe
	{
        const string NO_NAME = "no name";

		Triangle[] _triangles;
		double _scale = 1;
        double _rotateX;
        double _rotateY;
        double _rotateZ;
        string _name = NO_NAME;

		public Wireframe() {
		}

		public void Clear() {
			_scale = 1;
			_triangles = null;
            _name = NO_NAME;
		}

        [SuppressMessage("Microsoft.Performance", "CA1819", Justification = "Returning array directly for performance/simplicity reasons")]
        public Triangle[] Triangles {
			get { return _triangles; }
			set { _triangles = value; }
		}

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.ArgumentException.#ctor(System.String)")]
        public double Scale {
			get { return _scale; }
			set {
				if(0 >= value)
					throw new ArgumentException("Scale");
				_scale = value; 
			}
		}

        public string Name {
            get { return _name; }
            set { _name = value; }
        }

		public double ComputeScale(int width, int height, float fillratio) {
			double minx, miny, maxx, maxy;
			double scalex, scaley;

			if(null == _triangles)
				return 1;

			FindBoundingBox(out minx, out miny, out maxx, out maxy);
			width = (int)((float)width * fillratio);
			height = (int)((float)height * fillratio);
			scalex = width / (maxx - minx);
			scaley = height / (maxy - miny);
			return Math.Min(scalex, scaley);
		}

        public double RotateX {
            get { return _rotateX; }
            set { _rotateX = value; }
        }

        public double RotateY {
            get { return _rotateY; }
            set { _rotateY = value; }
        }

        public double RotateZ {
            get { return _rotateZ; }
            set { _rotateZ = value; }
        }

        [SuppressMessage("Microsoft.Design", "CA1021", Justification = "Returning results in output parameters for performance/simplicity reasons")]
		protected void FindBoundingBox(out double minx, out double miny, out double maxx, out double maxy) {
			if(null == _triangles)
				minx = miny = maxx = maxy = 0;

			minx = maxx = _triangles[0].P1.X;
			miny = maxy = _triangles[0].P1.Y;

			for(int i = 0; i < _triangles.Length; i++) {
				if(_triangles[i].P1.X < minx) minx = _triangles[i].P1.X;
				if(_triangles[i].P1.Y < miny) miny = _triangles[i].P1.Y;
				if(_triangles[i].P1.X > maxx) maxx = _triangles[i].P1.X;
				if(_triangles[i].P1.Y > maxy) maxy = _triangles[i].P1.Y;
				if(_triangles[i].P2.X < minx) minx = _triangles[i].P2.X;
				if(_triangles[i].P2.Y < miny) miny = _triangles[i].P2.Y;
				if(_triangles[i].P2.X > maxx) maxx = _triangles[i].P2.X;
				if(_triangles[i].P2.Y > maxy) maxy = _triangles[i].P2.Y;
				if(_triangles[i].P3.X < minx) minx = _triangles[i].P3.X;
				if(_triangles[i].P3.Y < miny) miny = _triangles[i].P3.Y;
				if(_triangles[i].P3.X > maxx) maxx = _triangles[i].P3.X;
				if(_triangles[i].P3.Y > maxy) maxy = _triangles[i].P3.Y;
			}
		}

        // render the wireframe on the surface provided in "g"
        // NOTE: this is probably the slowest and more simplistic way
        // of rendering a 3D wireframe. We don't even optimize shared
        // edges or do perspective correction. The goal is to keep this
        // as simple as possible, not a sophisticated drawing app.
		public void Render(Graphics g, Pen pen, int width, int height) {
			if(null == g)
				throw new ArgumentNullException("g");
			if(null == _triangles)
				return;

			int fixw = width / 2;
			int fixh = height / 2;
			Point[] points2d = new Point[4];
			for(int i = 0; i < _triangles.Length; i++) {
                Triangle t = _triangles[i];

                // apply any rotation if requested
                if(0 != _rotateX || 0 != _rotateY || 0 != _rotateZ)
                    t.Rotate(_rotateX, _rotateY, _rotateZ);

				points2d[0].X = (int)(t.P1.X * _scale + fixw);
				points2d[0].Y = (int)(- t.P1.Y * _scale + fixh);
				points2d[1].X = (int)(t.P2.X * _scale + fixw);
				points2d[1].Y = (int)(- t.P2.Y * _scale + fixh);
				points2d[2].X = (int)(t.P3.X * _scale + fixw);
				points2d[2].Y = (int)(- t.P3.Y * _scale + fixh);
				points2d[3] = points2d[0];
				g.DrawLines(pen, points2d);
			}
		}
	}
}
